/* Copyright 2008 Bas van den Berg
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "PlayerSession.h"
#include "Connection.h"
#include "Parser.h"
#include "config.h"
#include "Words.h"
#include "Utils.h"
#include "MudPlayer.h"
#include "Database.h"

using namespace Chronos;
using namespace std;

namespace Chronos {

class DummyConnection : public Connection {
public:
    virtual int read (char*, unsigned int) { return Connection::NODATA; }
    virtual void write(const char*) {}
    virtual void write(const char*, int) {}
    virtual void setEcho(bool) {}
};

}


PlayerSession::PlayerSession(const string& name_, Connection* con, Parser& parser_, Database& database_)
        : name(name_)
        , state(ACTIVE)
        , connection(con)
        , parser(parser_)
        , database(database_)
        , timeout(Config::SESSION_ACTIVE_TIMEOUT)
        , autoSaveInterval(Config::SESSION_AUTOSAVE)
        , logout(false)
        , lastCommand("")
        , player(MudPlayer::load(database, name, *this))
		, addPrompt(true)
        , prompt(database.getPlayerAttribute(player.getID(), "prompt"), player)
        , aliases(database.getPlayerAttribute(player.getID(), "aliases"))
        , loginTime(Utils::getPulse())
        , totalTime(database.getPlayerIntAttribute(player.getID(), "totalTime"))
{
    buffer << "Welcome " << name << '\n';
    buffer << "Type 'help' to see a list of commands.\n";

    initAliases();
    initSettings();

    player.setCredits(database.getPlayerIntAttribute(player.getID(), "credits"));
}


PlayerSession::~PlayerSession() {
    player.generateEvent(MUDPLAYER_LOGOUT);
    save();
    delete &player;
    flush();
	delete connection;
}


void PlayerSession::changeConnection(Connection* con) {
    buffer << "Warning: switching to new connection\n";
	flush();
    delete connection;
    connection = con;
    state = ACTIVE;
    timeout = Config::SESSION_ACTIVE_TIMEOUT;

    addPrompt = true;
    buffer << "<reconnected....welcome back>\n";
}


int PlayerSession::handlePulse() {
    if (state != DISCONNECTED) {
        handleInput();
        if (logout) return FINISHED;
    }
    checkAutoSave();
    if (timeout-- == 0) {
        switch (state) {
        case ACTIVE:
            buffer << "<You are now marked idle>\n";
            state = IDLE;
            timeout = Config::SESSION_IDLE_TIMEOUT;
            break;
        case IDLE:
            buffer << "Connection inactive... closing player session\n";
            logout = true;
        	return FINISHED;
        case DISCONNECTED:
            logout = true;
        	return FINISHED;
        };
    }
    return OK;
}


void PlayerSession::checkAutoSave() {
    if (autoSaveInterval-- == 0) {
        buffer << "(auto-saved)\n";
        save();
        autoSaveInterval = Config::SESSION_AUTOSAVE;
    }

}


StringBuilder& PlayerSession::write() { return buffer; }


void PlayerSession::flush() {
	if (!logout && addPrompt) prompt.print(buffer);
    connection->write(buffer, buffer.size());
    buffer.clear();
	addPrompt = false;
}


void PlayerSession::setPrompt(const string& p) { prompt.setPrompt(p); }


void PlayerSession::handleInput() {
    int size = connection->read(readBuffer, MAX_INPUT_LENGTH);
    if (size == Connection::NODATA) return;
    if (size == Connection::ERROR) {
        delete connection;
        connection = new DummyConnection();
        state = DISCONNECTED;
        timeout = Config::SESSION_DISCONNECT_TIMEOUT;
        return;
    }
    if (readBuffer[0] == -1) return;		// skip telnet signals

    if (state == IDLE) {
		buffer << "<Removed idle status>\n";
		state = ACTIVE;
	}
    timeout = Config::SESSION_ACTIVE_TIMEOUT;
	addPrompt = true;
    size = Utils::stripNewline(readBuffer);
    if (size == 0) return;

    if (size == 1 && readBuffer[0] == '!') {
        strcpy(readBuffer, lastCommand.c_str());	// repeat last command
    }
    lastCommand = aliases.replace(readBuffer);
    parser.parse(*this, lastCommand.c_str());
}


void PlayerSession::initAliases() {
    if (aliases.size() != 0) return;

    aliases.add("n", "north");
    aliases.add("e", "east");
    aliases.add("s", "south");
    aliases.add("w", "west");
    aliases.add("u", "up");
    aliases.add("d", "down");
    aliases.add("i", "inv");
    aliases.add("l", "look");
    aliases.add("la", "look at");
    aliases.add("kill", "attack");
    aliases.add("eq", "equip");
    aliases.add("st", "status");
    aliases.add("press", "push");
}


void PlayerSession::initSettings() {
    settings.add("descr", "brief", "long");
    settings.add("wimpymode", "on", "off");
    settings.initialize(database.getPlayerAttribute(player.getID(), "settings"));
}


void PlayerSession::save() {
    player.save(database);
    StringBuilder statement;
    statement << "update players set aliases='" << aliases.toString() << '\'';
    statement << ", settings='" << settings.toString() << '\'';
    statement << ", prompt='" << prompt.getPrompt() << '\'';
    statement << ", totalTime=" << getTotalTime();
    statement << " where id=" << player.getID() << ';';
    database.query(statement);
}


void PlayerSession::changePassword(const std::string& newPass) {
    StringBuilder statement;
    statement << "update players set password='" << newPass << '\'';
    statement << " where id=" << player.getID() << ';';
    database.query(statement);
}


unsigned int PlayerSession::getPlayTime() const { return Utils::getPulse() - loginTime; }

unsigned int PlayerSession::getTotalTime() const { return totalTime + getPlayTime(); }

